1#!/usr/bin/python
2# -*- coding:utf-8 -*-
3#
4# Python driver for GDEM035T81 3.5" e-Paper display
5# Controller: SSD1685
6# Resolution: 384x184
7#
8# Based on GxEPD2 driver by Jean-Marc Zingg:
9# https://github.com/ZinggJM/GxEPD2/blob/master/src/gdey/GxEPD2_290_GDEY029T71H.cpp
10# Adapted for Waveshare HAT on Raspberry Pi (spidev + RPi.GPIO)
11
12import RPi.GPIO as GPIO
13import spidev
14import time
15
16# Pin definitions (Waveshare HAT standard pinout)
17RST_PIN = 17
18DC_PIN = 25
19CS_PIN = 8
20BUSY_PIN = 24
21
22# Display resolution
23WIDTH = 184
24HEIGHT = 384
25
26# SSD1685 source shift (OTP set for wider panel; shift to align to actual TFT)
27SOURCE_SHIFT = 8 # adjust if image is offset horizontally
28
29
30class EPD:
31 def __init__(self):
32 self.reset_pin = RST_PIN
33 self.dc_pin = DC_PIN
34 self.cs_pin = CS_PIN
35 self.busy_pin = BUSY_PIN
36 self.width = WIDTH
37 self.height = HEIGHT
38 self._init_done = False
39
40 GPIO.setmode(GPIO.BCM)
41 GPIO.setwarnings(False)
42 GPIO.setup(self.reset_pin, GPIO.OUT)
43 GPIO.setup(self.dc_pin, GPIO.OUT)
44 GPIO.setup(self.cs_pin, GPIO.OUT)
45 GPIO.setup(self.busy_pin, GPIO.IN)
46
47 self.spi = spidev.SpiDev()
48 self.spi.open(0, 0)
49 self.spi.max_speed_hz = 4000000
50 self.spi.mode = 0b00
51
52 # ------------------------------------------------------------------ low level
53
54 def _reset(self):
55 GPIO.output(self.reset_pin, GPIO.HIGH)
56 time.sleep(0.2)
57 GPIO.output(self.reset_pin, GPIO.LOW)
58 time.sleep(0.002)
59 GPIO.output(self.reset_pin, GPIO.HIGH)
60 time.sleep(0.2)
61
62 def _send_command(self, cmd):
63 GPIO.output(self.dc_pin, GPIO.LOW)
64 GPIO.output(self.cs_pin, GPIO.LOW)
65 self.spi.writebytes([cmd])
66 GPIO.output(self.cs_pin, GPIO.HIGH)
67
68 def _send_data(self, data):
69 GPIO.output(self.dc_pin, GPIO.HIGH)
70 GPIO.output(self.cs_pin, GPIO.LOW)
71 if isinstance(data, int):
72 self.spi.writebytes([data])
73 else:
74 # send in chunks to avoid SPI buffer limits
75 for i in range(0, len(data), 4096):
76 self.spi.writebytes(data[i:i+4096])
77 GPIO.output(self.cs_pin, GPIO.HIGH)
78
79 def _wait_busy(self, timeout_ms=5000):
80 """BUSY pin: LOW = busy, HIGH = idle (SSD1685)"""
81 deadline = time.time() + timeout_ms / 1000.0
82 while GPIO.input(self.busy_pin) == GPIO.LOW:
83 if time.time() > deadline:
84 print("WARNING: busy timeout")
85 break
86 time.sleep(0.01)
87
88 # ------------------------------------------------------------------ RAM area
89
90 def _set_ram_area(self, x, y, w, h):
91 x += SOURCE_SHIFT
92 # x increase, y increase (normal scan direction)
93 self._send_command(0x11)
94 self._send_data(0x03)
95 # X start / end (byte addresses)
96 self._send_command(0x44)
97 self._send_data(x // 8)
98 self._send_data((x + w) // 8 - 1)
99 # Y start / end (two bytes each, LSB first)
100 self._send_command(0x45)
101 self._send_data(y % 256)
102 self._send_data(y // 256)
103 self._send_data((y + h - 1) % 256)
104 self._send_data((y + h - 1) // 256)
105 # Set RAM x/y counters
106 self._send_command(0x4E)
107 self._send_data(x // 8)
108 self._send_command(0x4F)
109 self._send_data(y % 256)
110 self._send_data(y // 256)
111
112 # ------------------------------------------------------------------ init
113
114 def init(self):
115 self._reset()
116 time.sleep(0.01)
117
118 self._send_command(0x12) # SWRESET
119 time.sleep(0.01)
120
121 # Driver output control
122 self._send_command(0x01)
123 self._send_data((self.height - 1) % 256)
124 self._send_data((self.height - 1) // 256)
125 self._send_data(0x00)
126
127 # Border waveform
128 self._send_command(0x3C)
129 self._send_data(0x05)
130
131 # Use built-in temperature sensor
132 self._send_command(0x18)
133 self._send_data(0x80)
134
135 # Display update control (normal, no bypass)
136 self._send_command(0x21)
137 self._send_data(0x00)
138 self._send_data(0x00)
139
140 self._set_ram_area(0, 0, self.width, self.height)
141 self._init_done = True
142
143 # ------------------------------------------------------------------ clear
144
145 def clear(self, color=0xFF):
146 """Clear screen. color=0xFF = white, 0x00 = black."""
147 if not self._init_done:
148 self.init()
149 buf = [color] * (self.width * self.height // 8)
150 # write to both current (0x24) and previous (0x26) buffers
151 for cmd in (0x26, 0x24):
152 self._set_ram_area(0, 0, self.width, self.height)
153 self._send_command(cmd)
154 self._send_data(buf)
155 self._refresh_full()
156
157 # ------------------------------------------------------------------ display
158
159 def getbuffer(self, image):
160 buf = [0xFF] * (self.width * self.height // 8)
161
162 image = image.convert('1')
163 image = image.rotate(90, expand=True)
164 image = image.resize((self.width, self.height))
165
166 pixels = image.load()
167
168 stride = (self.width + 7) // 8 # <-- FIX
169
170 for y in range(self.height):
171 for x in range(self.width):
172 if pixels[x, y] == 0:
173 index = (x // 8) + y * stride
174 buf[index] &= ~(0x80 >> (x % 8))
175
176 return buf
177
178 def display(self, buf):
179 """Send buffer and do a full refresh."""
180 if not self._init_done:
181 self.init()
182 for cmd in (0x26, 0x24): # <-- IMPORTANT
183 self._set_ram_area(0, 0, self.width, self.height)
184 self._send_command(cmd)
185 self._send_data(buf)
186 self._refresh_full()
187
188 def display_partial(self, buf):
189 """Send buffer and do a fast partial refresh."""
190 if not self._init_done:
191 self.init()
192 self._set_ram_area(0, 0, self.width, self.height)
193 self._send_command(0x24)
194 self._send_data(buf)
195 self._refresh_partial()
196
197 # ------------------------------------------------------------------ refresh
198
199 def _refresh_full(self):
200 # bypass RED channel, use fast full update with temperature
201 self._send_command(0x21)
202 self._send_data(0x40) # bypass RED as 0
203 self._send_data(0x00)
204 # write temperature register (110 dec = 0x6E = ~110°C forces fast LUT)
205 self._send_command(0x1A)
206 self._send_data(0x6E)
207 self._send_data(0x00)
208 # load temperature value
209 self._send_command(0x22)
210 self._send_data(0x91)
211 self._send_command(0x20)
212 time.sleep(0.002)
213 # full update sequence
214 self._send_command(0x22)
215 self._send_data(0xC7)
216 self._send_command(0x20)
217 self._wait_busy(5000)
218
219 def _refresh_partial(self):
220 self._send_command(0x21)
221 self._send_data(0x00)
222 self._send_data(0x00)
223 self._send_command(0x22)
224 self._send_data(0xDC)
225 self._send_command(0x20)
226 self._wait_busy(1000)
227
228 # ------------------------------------------------------------------ power
229
230 def sleep(self):
231 self._send_command(0x10) # deep sleep
232 self._send_data(0x01)
233 time.sleep(2)
234
235 def close(self):
236 self.spi.close()
237 GPIO.cleanup()
238
239
240
241if __name__ == '__main__':
242 import logging
243 logging.basicConfig(level=logging.INFO)
244 from PIL import Image
245
246 epd = EPD()
247 print("init...")
248 epd.init()
249 print("clear...")
250 epd.clear()
251 print("loading image...")
252 img = Image.open('test.png')
253 print("displaying...")
254 epd.display(epd.getbuffer(img))
255 print("sleep...")
256 epd.sleep()
257 epd.close()
258 print("done!")